-- source compiler for wTest
-- The MIT License (MIT)
-- Copyright (c) 2016 Yanghe Sun

runner = require("runner")
path   = require("path")
fs     = require("path.fs")
info   = require("path.info")

import sub, gsub from string

default_rules = {
    cpp: { cmd: "g++ *.cpp -O2 -o *.out", exec: "*.out", order: 1 },
    c: { cmd: "gcc *.c -O2 -o *.out", exec: "*.out", order: 2 },
    pas: { cmd: "fpc *.pas", exec:  "*", order: 3 }
}

extend = (x, y) ->
    for k, v in pairs(y)
        x[k] = v

split = (str, sep = '\t') ->
    fields = {}
    pattern = string.format("([^%s]+)", sep)
    gsub(str, pattern, (c) -> fields[#fields + 1] = c)
    return fields

exeable = (p) ->
    return fs.exists(p) if info.platform == "linux"
    return fs.exists(p) or fs.exists(p .. ".exe") if info.platform == "windows"

system_path = split(os.getenv("PATH"), info.pathsep)

class compiler
    new: =>
        @rules = {k, v for k, v in pairs(default_rules)}
        @path = [item for item in *system_path]

    _Which: (target) =>
        for item in *@path
            paths = path.join(item,target)
            return paths if exeable(paths)
        return nil

    _GetRawCommand: (source) =>
        name, ext = path.splitext(source)
        ext = sub(ext, 2)
        v = @rules[ext]
        return gsub(v.cmd, "*", name), gsub(v.exec, "*", name) if @rules[ext]
        return nil

    LoadRules: (conf, init) =>
        new_rules = dofile(conf)
        @rules = {} if init
        extend(@rules, new_rules)

    Select: (title, srcs) =>
        selection = { order: 1e10 , src: "" }
        for item in *srcs
            base = select(2, path.split(item))
            ext = sub(select(2, path.splitext(base)), 2)
            if path.splitext(base) == title and (@rules[ext].order < selection.order) then
                with selection
                    .order = @rules[ext].order
                    .src = item

        if selection.src == ""
            return nil
        return selection.src

    Compile: (source) =>
        tmp_folder = fs.tmpdir!
        _source = path.join(tmp_folder, select(2, path.split(source)))
        fs.copy(source, _source)

        command, output = @_GetRawCommand(_source)
        commands = split(command, " ")
        commands[1] = @_Which(commands[1])
        exe = commands[1]
        error_dump = os.tmpname!
        io.open(error_dump,"w")\close!

        config = {
            dump_mask: 1,
            max_cpu_time: runner._UNLIMITED,
            max_memory: runner._UNLIMITED,
            error_path: error_dump,
            exe_path: exe,
            args: commands
        }

        dump_handle = io.open(error_dump)
        ret = {
            exe_path: output,
            success: runner.Run(config).flag == runner._RESULT_SUCCESS,
            tmp_folder: tmp_folder,
            error_info: dump_handle\read("*a")
        }

        ret._Gc = => fs.removedirs(self.tmp_folder)
        setmetatable(ret, { __gc: ret._Gc })
        dump_handle\close!
        fs.remove(error_dump)

        return ret

return compiler
